/*
 * =====================================================================================
 *
 *       Filename:  cl_signal_handler.cxx
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  03/27/2011 10:12:21 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Chen Ken (Ken), zlchen.ken@gmail.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#include <strings.h>
#include <algorithm>
#include <cassert>
#include "cl_signal_handler.hxx"
#include "cl_thread.hxx"
#include "cl_mutex.hxx"
#include "cl_lock_guard.hxx"
  
namespace neo
{
using namespace std;

namespace
{
bool signalSet(const CLSignalSet& sigs, sigset_t* sigSet, bool addOriginal = true) 
{
  ::sigemptyset(sigSet);

  // First get the old mask.
  if(addOriginal && ::pthread_sigmask(SIG_SETMASK, NULL, sigSet) < 0) 
  {
  	return false;
  }

  // Add the new one.
  CLSignalSet::const_iterator itr = sigs.begin();
  CLSignalSet::const_iterator itrEnd = sigs.end();
  for(;itr != itrEnd; ++itr)
  {
    ::sigaddset(sigSet, *itr);
  }

  return true;
}

bool blockSignals(const CLSignalSet& sigs)
{
  sigset_t sigSet;
  if(!signalSet(sigs, &sigSet))
  {
    return false;
  }

  if(::pthread_sigmask(SIG_BLOCK, &sigSet, NULL) < 0)
  {
  	return false;
  }

  return true;
}

bool unblockSignals(const CLSignalSet& sigs)
{
  sigset_t sigSet; 
  if(!signalSet(sigs, &sigSet, false)) 
  {
  	return false;
  }

  if(::pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL) < 0)
  {
  	return false;
  }

  return true;
}

bool isSignalCannotBlock(int sigNum)
{
  // TODO, add more...
  if((sigNum == SIGSEGV) || sigNum == SIGABRT)
  {
    return true;
  }

  return false;
}
} // namespace

///static members
CLSignalHandler::SigactionMap CLSignalHandler::m_origSigactionForSpecial;
CLSignalHandler::SignalHandlerMap CLSignalHandler::m_signalHandlersForSpecial;
unique_ptr<CLLock> CLSignalHandler::m_lockForSpecial(new CLMutex);

CLSignalHandler::CLSignalHandler(bool asyncMode)
  : m_asyncMode(asyncMode),
  m_lock(new CLMutex) 
{
  if(m_asyncMode)
  {
    m_thr = unique_ptr<CLThread>(new CLThread(bind(&CLSignalHandler::dispatch, this)));
  }
}

CLSignalHandler::~CLSignalHandler() 
{
  unblockSignals(m_blockedSignals);
}

bool CLSignalHandler::installHandlerFor(int sigNum, const CLCallbackFunctor& cb) 
{
  if(isSignalCannotBlock(sigNum))
  {
    // for SEGV, ABORT...
  	return installHandlerForSpecial(sigNum, cb);
  }

  CLLockGuard guard(*m_lock);
  SignalHandlerMap::iterator itr = m_installedSignalHandlers.find(sigNum);
  if(itr == m_installedSignalHandlers.end())
  {
  	CLSignalSet sigs(1, sigNum);
    if(!blockSignals(sigs))
    {
  	  return false;
    }

    CallbackVec callbacks(1, cb);
  	m_installedSignalHandlers.insert(make_pair(sigNum, callbacks));
    m_blockedSignals.push_back(sigNum);
  }
  else
  {
  	itr->second.push_back(cb);
  }

  return true;
}

bool CLSignalHandler::uninstallHandlerFor(int sigNum) 
{
  if(isSignalCannotBlock(sigNum))
  {
    // for SEGV, ABORT...
  	return uninstallHandlerForSpecial(sigNum);
  }

  CLSignalSet unblockSigs(1, sigNum);
  if(!unblockSignals(unblockSigs))
  {
  	return false;
  }

  CLLockGuard guard(*m_lock);
  m_installedSignalHandlers.erase(sigNum);
  CLSignalSet::iterator itr = find(m_blockedSignals.begin(), m_blockedSignals.end(), sigNum);
  if(itr != m_blockedSignals.end()) 
  {
    m_blockedSignals.erase(itr); 
  }

  return true;
}

void CLSignalHandler::waitForSignal(void)
{
  if(m_asyncMode)
  {
    m_thr->start();
  }
  else
  {
    dispatch();
  }
}

void CLSignalHandler::dispatch(void)
{
  int res = 0, sigNum = 0;
  for(;;)
  {
    sigset_t sigSet; 
  	if(!signalSet(blockedSignals(), &sigSet, false))
  	{
  		continue;
  	}

  	if((res = ::sigwait(&sigSet, &sigNum)) == 0)
  	{
  	  doDispatch(sigNum);
  	}
  }
}


// We don't need hold the mutex anymore, coz we are dieing...
// and we can easily run into dead lock.
void CLSignalHandler::dispatchForSpecial(int sigNum)
{
  SignalHandlerMap::iterator itr = m_signalHandlersForSpecial.find(sigNum);
  if(itr != m_signalHandlersForSpecial.end())
  {
    doCallbacks(itr->second);
    doUninstallHandlerForSpecial(sigNum);
  }
}

void CLSignalHandler::doDispatch(int sigNum) 
{
  CallbackVec callbacks;
  {// force the lock section as narrow as possible.
  	CLLockGuard guard(*m_lock);
  	SignalHandlerMap::iterator itr = m_installedSignalHandlers.find(sigNum);
  	if(itr != m_installedSignalHandlers.end())
  	{
      callbacks = itr->second;
  	}
  }
  
  doCallbacks(callbacks);
}

void CLSignalHandler::doCallbacks(CallbackVec& callbacks)
{
  CallbackVec::iterator callItr = callbacks.begin();
  CallbackVec::iterator callItrEnd = callbacks.end();
  for(;callItr != callItrEnd; ++callItr)
  {
  	(*callItr)();
  }
}

const CLSignalSet CLSignalHandler::blockedSignals(void) const
{
  CLLockGuard guard(*m_lock);
  CLSignalSet sigs = m_blockedSignals;

  return sigs;
}

bool CLSignalHandler::installHandlerForSpecial(int sigNum, const CLCallbackFunctor& cb)
{
  CLLockGuard guard(*m_lockForSpecial);
  SignalHandlerMap::iterator itr = m_signalHandlersForSpecial.find(sigNum);
  if(itr == m_signalHandlersForSpecial.end())
  {
  	if(!doInstallHandlerForSpecial(sigNum))
  	{
  		return false;
  	}

    CallbackVec callbacks(1, cb);
  	m_signalHandlersForSpecial.insert(make_pair(sigNum, callbacks));
  }
  else
  {
    itr->second.push_back(cb);
  }

  return true;
}

bool CLSignalHandler::doInstallHandlerForSpecial(int sigNum)
{
  struct sigaction act;
  struct sigaction oact;
  ::bzero(&act, sizeof(act));
  act.sa_handler = CLSignalHandler::dispatchForSpecial;
  sigemptyset(&act.sa_mask);
  if(::sigaction(sigNum, &act, &oact) < 0)
  {
  	return false;
  }

  // Save the old sigaction for sigNum
  m_origSigactionForSpecial.insert(make_pair(sigNum, oact));
  return true;
}

bool CLSignalHandler::uninstallHandlerForSpecial(int sigNum)
{
  CLLockGuard guard(*m_lockForSpecial);
  SignalHandlerMap::iterator itr = m_signalHandlersForSpecial.find(sigNum);
  if(itr != m_signalHandlersForSpecial.end())
  {
  	if(!doUninstallHandlerForSpecial(sigNum))
  	{
  		return false;
  	}

    m_signalHandlersForSpecial.erase(itr);
  }
  else
  {
    return false;
  }

  return true;
}

bool CLSignalHandler::doUninstallHandlerForSpecial(int sigNum)
{
  SigactionMap::iterator itr = m_origSigactionForSpecial.find(sigNum);
  if(itr != m_origSigactionForSpecial.end())
  {
    if(::sigaction(sigNum, &itr->second, NULL) < 0)
    {
  	  return false;
    }
    // Clear the old sigaction for sigNum from the map;
    m_origSigactionForSpecial.erase(itr);
  }
  else
  {
    return false;
  }

  return true;
}


/////////
CLBlockSignal::CLBlockSignal(const CLSignalSet& sigSet)
  : m_sigSet(sigSet)
{
  blockSignals(m_sigSet);
}

CLBlockSignal::~CLBlockSignal()
{
  unblockSignals(m_sigSet);
}

} // neo
